#pragma once
#include <string>
#include <vector>
#include <future>
#include <fstream>
#define TIXML_USE_STL
#include "include/tinyxml.h"
using namespace::std;

#ifdef _DEBUG
#pragma comment(lib, "lib/tinyxmlSTL_dbg.lib")
#else
#pragma comment(lib, "lib/tinyxmlSTL.lib")
#endif


struct stXMLNote
{
    string NoteName;
    string NoteValue;
};
class TiXmlDeclaration;
class TiXmlElement;
class CXmlEncoder
{
public:
    CXmlEncoder() :m_pDoc(NULL), m_pRoot(NULL){
    }
    virtual ~CXmlEncoder(){}
    TiXmlElement* CreateRoot(const char* pRootName)
    {
        m_pDoc = new TiXmlDeclaration("1.0", "UTF-8", "");
        m_pRoot = new TiXmlElement(pRootName);
        return m_pRoot;
    }

    TiXmlElement* AddElement(TiXmlElement* pParentElem, const char* name)
    {
        TiXmlElement newNode(name);
        return dynamic_cast<TiXmlElement*>(pParentElem->InsertEndChild(newNode));
    }
    void AddAttribute(TiXmlElement* pCurElem, const char* name, int value)
    {
        pCurElem->SetAttribute(name, value);
    }
    void AddAttribute(TiXmlElement* pCurElem, const char* name, const string& value)
    {
        pCurElem->SetAttribute(name, value);
    }

    void Finish()
    {
        TiXmlDocument doc;
        TiXmlPrinter xmlPrinter;
        doc.LinkEndChild(m_pDoc);
        doc.LinkEndChild(m_pRoot);
        doc.Accept(&xmlPrinter);
        m_strResult = xmlPrinter.CStr();
    }

    string getResult(){ return m_strResult; }

private:
    string m_strResult;
    TiXmlNode* m_pDoc;
    TiXmlElement* m_pRoot;
};

class TiXmlDocument;
class CXmlDecoder
{
public:
    CXmlDecoder() :m_pDoc(nullptr){}
    bool ReadFile(const string& filepath)
    {
        m_pDoc = new TiXmlDocument(filepath.c_str());
        return m_pDoc->LoadFile();
    }

    TiXmlElement* GetLeafNoteElement(const string& path, const char *split)
    {
        if (!m_pDoc)
            return NULL;
        TiXmlNode* tempNode = m_pDoc;
        string::size_type pos1 = 0, pos2 = 0;
        do
        {
            pos2 = path.find(split, pos1);
            string curPath = path.substr(pos1, pos2 - pos1);
            pos1 = pos2 + 1;
            if (!tempNode)
                break;
            tempNode = tempNode->FirstChild(curPath.c_str());
        } while (string::npos != pos2);
        return dynamic_cast<TiXmlElement*>(tempNode);
    }

    TiXmlElement* GetLeafNoteElement(TiXmlNode* tempNode, const string& path, const char *split)
    {
        if (!m_pDoc)
            return NULL;
        string::size_type pos1 = 0, pos2 = 0;
        do
        {
            pos2 = path.find(split, pos1);
            string curPath = path.substr(pos1, pos2 - pos1);
            pos1 = pos2 + 1;
            if (!tempNode)
                break;
            tempNode = tempNode->FirstChild(curPath.c_str());
        } while (string::npos != pos2);
        return dynamic_cast<TiXmlElement*>(tempNode);
    }
    bool QueryBoolAttr(const TiXmlElement* element, const char* name)
    {
        if (!element)
            return 0;
        bool value = 0;
        element->QueryBoolAttribute(name, &value);
        return value;
    }
    int QueryIntAttr(const TiXmlElement* element, const char* name)
    {
        if (!element)
            return 0;
        int value = 0;
        element->QueryIntAttribute(name, &value);
        return value;
    }
    double QueryDoubleAttr(const TiXmlElement* element, const char* name)
    {
        if (!element)
            return 0;
        double value = 0;
        element->QueryDoubleAttribute(name, &value);
        return value;
    }
    string QueryStringAttr(const TiXmlElement* element, const char* name)
    {
        if (!element)
            return "";
        std::string value;
        element->QueryStringAttribute(name, &value);
        return value;
    }
    void Release()
    {
        if (m_pDoc)
        {
            delete m_pDoc;
            m_pDoc = nullptr;
        }
    }
private:
    TiXmlDocument* m_pDoc;
};


class TiXmlDocument;
class CXmlParser
{
public:
    //begin for write
    TiXmlElement* CreateRoot(const char* pRootName)
    {
        return m_xmlEncoder.CreateRoot(pRootName);
    }

    TiXmlElement* AddElement(TiXmlElement* pParentElem, const char* name)
    {
        return m_xmlEncoder.AddElement(pParentElem, name);
    }
    void AddAttribute(TiXmlElement* pCurElem, const char* name, int value)
    {
        m_xmlEncoder.AddAttribute(pCurElem, name, value);
    }
    void AddAttribute(TiXmlElement* pCurElem, const char* name, const string& value)
    {
        m_xmlEncoder.AddAttribute(pCurElem, name, value);
    }

    void Finish()
    {
        m_xmlEncoder.Finish();
    }

    string getResult(){ return m_xmlEncoder.getResult(); }
    //end for write

    //for read
    bool ReadFile(const string& filepath)
    {
        return m_xml.ReadFile(filepath);
    }

    TiXmlElement* GetLeafNoteElement(const string& path, const char *split)
    {
        return m_xml.GetLeafNoteElement(path, split);
    }

    TiXmlElement* GetLeafNoteElement(TiXmlNode* pParentNode, const string& path, const char *split)
    {
        return m_xml.GetLeafNoteElement(pParentNode, path, split);
    }
    bool QueryBoolAttr(const TiXmlElement* element, const char* name)
    {
        return m_xml.QueryBoolAttr(element, name);
    }
    int QueryIntAttr(const TiXmlElement* element, const char* name)
    {
        return m_xml.QueryIntAttr(element, name);
    }
    double QueryDoubleAttr(const TiXmlElement* element, const char* name)
    {
        return m_xml.QueryDoubleAttr(element, name);
    }
    string QueryStringAttr(const TiXmlElement* element, const char* name)
    {
        return m_xml.QueryStringAttr(element, name);
    }
    void Release()
    {
        m_xml.Release();
    }
    //end for wirte
private:
    CXmlEncoder m_xmlEncoder;
    CXmlDecoder m_xml;
};


class XmlSerializer;
struct IXmlSerializable
{
    virtual void Serialize(XmlSerializer * serializer, TiXmlElement* pParentElem, bool serialize) = 0;
};

class XmlSerializer
{
    CXmlParser m_xml;
    //CXmlEncoder m_xmlEncoder;
public:
    XmlSerializer(){}
    virtual ~XmlSerializer(){ Close(); }

    bool OpenSerializer(const char* fileapth)
    {
        return m_xml.ReadFile(fileapth);
    }

    TiXmlElement* Create(const char* rootname)
    {
        return m_xml.CreateRoot(rootname);
    }
    void save(const char* filepath)
    {
        m_xml.Finish();
        std::ofstream ofs(filepath, ios::out | ios::trunc);
        if (ofs.is_open())
        {
            ofs.write(m_xml.getResult().c_str(), m_xml.getResult().size());
            ofs.close();
        }
    }
    //bool OpenDeserializer(IStream * stream){}
    void Close()
    {
        m_xml.Release();
    }

    TiXmlElement* GetLeafNoteElement(const string& leafpath)
    {
        return m_xml.GetLeafNoteElement(leafpath, "/");
    }

    TiXmlElement* GetLeafNoteElement(TiXmlElement* base, const string& leafpath)
    {
        return m_xml.GetLeafNoteElement(base, leafpath, "/");
    }

    TiXmlElement* GetSiblingElement(TiXmlElement* Sibling, const string& leafpath="")
    {
        return Sibling->NextSiblingElement();
    }

    virtual void SerializeElement(TiXmlElement* pCurNote, const string & elementName, IXmlSerializable & var)
    {
        auto newElement = (TiXmlElement*)m_xml.AddElement(pCurNote, elementName.c_str());
        var.Serialize(this, newElement, true);
    }
    template<class T>
    void SerializeElement(TiXmlElement* pCurNote, const string & elementName, std::vector<T> varlist)
    {
        for (IXmlSerializable & item : varlist)
        {
            auto newElement = m_xml.AddElement(pCurNote, elementName.c_str());
            item.Serialize(this, newElement, true);
        }
    }

    virtual void SerializeAttribute(TiXmlElement* pCurNote, const string & attributeName, bool var)
    {
        m_xml.AddAttribute(pCurNote, attributeName.c_str(), var ? "true" : "false");
    }
    virtual void SerializeAttribute(TiXmlElement* pCurNote, const string & attributeName, int var)
    {
        m_xml.AddAttribute(pCurNote, attributeName.c_str(), std::to_string(var));
    }
    virtual void SerializeAttribute(TiXmlElement* pCurNote, const string & attributeName, double var)
    {
        m_xml.AddAttribute(pCurNote, attributeName.c_str(), std::to_string(var));
    }
    virtual void SerializeAttribute(TiXmlElement* pCurNote, const string & attributeName, const string & var)
    {
        m_xml.AddAttribute(pCurNote, attributeName.c_str(), var.c_str());
    }

    virtual void DeserializeElement(TiXmlElement* pParentNote, const string & elementName, IXmlSerializable & var)
    {
        TiXmlElement* pCurNote = GetLeafNoteElement(pParentNote, elementName);
        var.Serialize(this, pCurNote, false);
    }

    template<class T>
    void DeserializeElement(TiXmlElement* pParentNote, const string & elementName, std::vector<T>& var)
    {
        TiXmlElement* pCurNote = GetLeafNoteElement(pParentNote, elementName);
        while (pCurNote != nullptr)
        {
            T item;
            item.Serialize(this, pCurNote, false);
            var.push_back(item);
            pCurNote = pCurNote->NextSiblingElement();
        }
    }

    virtual void DeserializeAttribute(TiXmlElement* pCurNote, const string & attributeName, bool & var)
    {
        var = m_xml.QueryBoolAttr(pCurNote, attributeName.c_str());
    }                                  
    virtual void DeserializeAttribute(TiXmlElement* pCurNote,  const string & attributeName, int & var)
    {
        var = m_xml.QueryIntAttr(pCurNote, attributeName.c_str());
    }
    virtual void DeserializeAttribute(TiXmlElement* pCurNote, const string & attributeName, double & var)
    {
        var = m_xml.QueryDoubleAttr(pCurNote, attributeName.c_str());
    }
    virtual void DeserializeAttribute(TiXmlElement* pCurNote,  const string & attributeName, string & var)
    {
        var = m_xml.QueryStringAttr(pCurNote, attributeName.c_str());
    }
    //get content
    virtual void DeserializeAttributeValue(TiXmlElement* pCurNote, int & var)
    {
        var = 0;
        if (pCurNote &&pCurNote->FirstChild())
        {
            string temp = pCurNote->FirstChild()->Value();
            var = atoi(temp.c_str());
        }
    }
    virtual void DeserializeAttributeValue(TiXmlElement* pCurNote, bool & var){ /*TODO*/}
    virtual void DeserializeAttributeValue(TiXmlElement* pCurNote, double & var){ /*TODO*/ }
    virtual void DeserializeAttributeValue(TiXmlElement* pCurNote, string & var){ /*TODO*/ }
};


#define BEGIN_XML_SERIALIZER() \
    virtual void Serialize(XmlSerializer * serializer, TiXmlElement* pCurElem, bool serialize) \
    {                                                                  

#define END_XML_SERIALIZER()    \
    }


#define XML_ELEMENT(element, var)        \
    if (serialize)          \
    {                       \
        serializer->SerializeElement(pCurElem, #element, var); \
    }   \
    else \
    {   \
        serializer->DeserializeElement(pCurElem, #element, var);   \
    }

#define XML_ARRAYELEMENT(element, var) \
    if (serialize)          \
    {                       \
        serializer->SerializeElement<element>(pCurElem, #element, var); \
    }\
    else \
    { \
        serializer->DeserializeElement<element>(pCurElem, #element, var); \
    }


#define XML_ATTRIBUTE(element, var)      \
    if (serialize)          \
    {                       \
        serializer->SerializeAttribute(pCurElem, #element, var); \
    }   \
    else \
    { \
        serializer->DeserializeAttribute(pCurElem, #element, var); \
    }

#define XML_ELEMENT_VALUE(var)        \
    if (serialize)          \
    {                       \
    }   \
    else \
    {   \
        serializer->DeserializeAttributeValue(pCurElem, var);   \
    }


class Item : public IXmlSerializable
{
public:
    BEGIN_XML_SERIALIZER()
        XML_ATTRIBUTE(age, Age)
        XML_ATTRIBUTE(name, Name)
        XML_ATTRIBUTE(type, Type)
        XML_ELEMENT_VALUE(Content)
    END_XML_SERIALIZER()
    int Age;
    int Type;
    string Name;
    int Content;
};


class ProductInfo : public IXmlSerializable
{
public:
    BEGIN_XML_SERIALIZER()
        XML_ATTRIBUTE(id, Id)
        XML_ATTRIBUTE(price, Price)
        XML_ATTRIBUTE(name, Name)
        XML_ARRAYELEMENT(Item, Items)
    END_XML_SERIALIZER()
public:
    int Id;
    double Price;
    string Name;
    std::vector<Item> Items;
};


class Params : public IXmlSerializable
{
public:
    BEGIN_XML_SERIALIZER()
        XML_ATTRIBUTE(id, Id)
        XML_ARRAYELEMENT(ProductInfo, ProductInfoList)
    END_XML_SERIALIZER()

public:
    std::vector<ProductInfo> ProductInfoList;
    int Id;
};


//for encode
class Team : public IXmlSerializable
{
public:
    BEGIN_XML_SERIALIZER()
    XML_ATTRIBUTE(id, Id)
    END_XML_SERIALIZER()
public:
    int Id;
};

class Software : public IXmlSerializable
{
public:
    BEGIN_XML_SERIALIZER()
        XML_ATTRIBUTE(id, Id)
        XML_ELEMENT(Team, Item)
    END_XML_SERIALIZER()
public:
    int Id;
    Team Item;
};


class ProjectSerial : public IXmlSerializable
{
public:
    BEGIN_XML_SERIALIZER()
        XML_ATTRIBUTE(id, Id)
        XML_ATTRIBUTE(name, Name)
        XML_ARRAYELEMENT(Software, Items)
    END_XML_SERIALIZER()
public:
    int Id;
    string Name;
    std::vector<Software> Items;
};

void test_xmlserializer()
{
    //decode
    {
        XmlSerializer serial;
        bool ret = serial.OpenSerializer("data\\input.xml");
        Params params;
        params.Serialize(&serial, serial.GetLeafNoteElement("Params"), false);
        int gameover = 0;
    }
    //encode
    {
        XmlSerializer serial;
        ProjectSerial params;
        params.Id = 100;
        params.Name = "Galileo";
        Software s1; s1.Id = 101; 
        Software s2; s2.Id = 102;
        s1.Item.Id = 1101;
        s2.Item.Id = 1102;
        params.Items.push_back(s1);
        params.Items.push_back(s2);
        TiXmlElement* root = serial.Create("Project");
        params.Serialize(&serial, root, true);
        serial.save("data\\output.xml");
        int gameoer = 0;
    }

    int over = 0;
}